package com.small.nacos.config.server.service;


import com.small.nacos.common.notify.NotifyCenter;
import small.common.core.constants.CommonConstants;
import small.common.crypto.util.KeyUtil;
import small.common.crypto.util.MD5Util;
import small.common.core.util.StringUtil;
import com.small.nacos.config.server.model.CacheItem;
import com.small.nacos.config.server.model.event.LocalDataChangeEvent;
import com.small.nacos.config.server.utils.DiskUtil;
import com.small.nacos.sys.utils.PropertyUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author zhoujin
 * @Date 2022/11/23 13:35
 */
public class ConfigCacheService {

    protected static final Logger LOGGER = LoggerFactory.getLogger(ConfigCacheService.class);

    private static final ConcurrentHashMap<String, CacheItem> CACHE = new ConcurrentHashMap<String, CacheItem>();

    public static CacheItem getContentCache(String groupKey) {
        return CACHE.get(groupKey);
    }

    /**
     * 保存配置文件并更新缓存中的md5值
     * @param dataId
     * @param group
     * @param tenant
     * @param content
     * @param lastModifiedTs
     * @param type
     * @return
     */
    public static boolean dump(String dataId, String group, String tenant, String content, long lastModifiedTs,
                               String type) {
        String groupKey = KeyUtil.getKey(dataId, group, tenant);
        CacheItem cacheItem = makeSure(groupKey);
        cacheItem.setType(type);
        final int lockResult = tryWriteLock(groupKey);
        //未获取到写锁
        if (0 == lockResult) {
            LOGGER.warn("[dump-error] {} not exist.", groupKey);
            return false;
        }
        //获取锁失败
        if (lockResult < 0) {
            LOGGER.warn("[dump-error] write lock failed. {}", groupKey);
            return false;
        }
        try {
            final String md5 = MD5Util.md5Hex(content, CommonConstants.ENCODE);
            if (md5.equals(ConfigCacheService.getContentMd5(groupKey)) && DiskUtil.targetFile(dataId, group, tenant).exists()) {
                LOGGER.warn("[dump-ignore] ignore to save cache file. groupKey={}, md5={}, lastModifiedOld={}, "
                                + "lastModifiedNew={}", groupKey, md5, cacheItem.getLastModifiedTs(),
                        lastModifiedTs);
            } else if (!PropertyUtil.isDirectRead()){
                //保存配置文件
                DiskUtil.saveToDisk(dataId, group, tenant, content,false);
            }
            //更新md5 发布本地更新时间
            updateMd5(groupKey, md5, lastModifiedTs);
            return true;
        }catch (IOException ioe){
            LOGGER.error("[dump-exception] save disk error. " + groupKey + ", " + ioe.toString(), ioe);
            return false;
        }finally {
            releaseWriteLock(groupKey);
        }

    }


    /**
     * 获取配置MD5
     */
    public static String getContentMd5(String groupKey) {
        CacheItem item = CACHE.get(groupKey);
        if (item != null){
            return item.getMd5();
        }
        return CommonConstants.NULL;
    }

    public static boolean remove(String dataId, String group, String tenant) {
        final String groupKey = KeyUtil.getKey(dataId, group, tenant);
        final int lockResult = tryWriteLock(groupKey);


        if (0 == lockResult) {
            LOGGER.info("[remove-ok] {} not exist.", groupKey);
            return true;
        }


        if (lockResult < 0) {
            LOGGER.warn("[remove-error] write lock failed. {}", groupKey);
            return false;
        }
        try {
            if (!PropertyUtil.isDirectRead()) {
                DiskUtil.removeConfigInfo(dataId, group, tenant);
            }
            CACHE.remove(groupKey);
            NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey));

            return true;
        } catch (IOException ioe) {
            LOGGER.error("[dump-exception] remove disk error. " + groupKey + ", " + ioe.toString(), ioe);
            return false;
        } finally {
            releaseWriteLock(groupKey);
        }
    }

    /**
     * 更新MD5
     *
     * @param groupKey       groupKey
     * @param md5            md5
     * @param lastModifiedTs 最后更新时间
     */
    public static void updateMd5(String groupKey, String md5, long lastModifiedTs) {
        CacheItem cache = makeSure(groupKey);
        if (cache.md5 == null || !cache.md5.equals(md5)) {
            cache.md5 = md5;
            cache.lastModifiedTs = lastModifiedTs;
            NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey));
        }
    }

    /**
     * 获取读锁
     *
     * @param groupKey
     * @return
     */
    public static int tryReadLock(String groupKey) {
        CacheItem groupItem = CACHE.get(groupKey);
        int result = (null == groupItem) ? 0 : (groupItem.rwLock.tryReadLock() ? 1 : -1);
        if (result < 0) {
            LOGGER.warn("[read-lock] failed, {}, {}", result, groupKey);
        }
        return result;
    }

    /**
     * 释放读锁
     *
     * @param groupKey
     */
    public static void releaseReadLock(String groupKey) {
        CacheItem item = CACHE.get(groupKey);
        if (null != item) {
            item.rwLock.releaseReadLock();
        }
    }

    /**
     * 获取写锁
     *
     * @param groupKey
     * @return
     */
    static int tryWriteLock(String groupKey) {
        CacheItem groupItem = CACHE.get(groupKey);
        int result = (null == groupItem) ? 0 : (groupItem.rwLock.tryWriteLock() ? 1 : -1);
        if (result < 0) {
            LOGGER.warn("[write-lock] failed, {}, {}", result, groupKey);
        }
        return result;
    }

    /**
     * 释放写锁
     *
     * @param groupKey
     */
    static void releaseWriteLock(String groupKey) {
        CacheItem groupItem = CACHE.get(groupKey);
        if (null != groupItem) {
            groupItem.rwLock.releaseWriteLock();
        }
    }


    /**
     * 获取缓存
     *
     * @param groupKey
     * @return
     */
    public static CacheItem makeSure(final String groupKey) {
        CacheItem item = CACHE.get(groupKey);
        if (null != item) {
            return item;
        }
        CacheItem tmp = new CacheItem(groupKey);
        item = CACHE.putIfAbsent(groupKey, tmp);
        return (null == item) ? tmp : item;
    }

    public static boolean isUptodate(String groupKey, String md5) {
        String serverMd5 = getContentMd5(groupKey);
        if(StringUtil.isEmpty(md5) || StringUtil.isEmpty(serverMd5)){
            return false;
        }
        return serverMd5.equals(md5);
    }


}
